Medium 清新閱讀版
:連結
今天我們來聊聊「Mocking」吧!(話說30天已經過了一半了!)
所謂的 Mocking
,是指用各種方式來模擬它原本的行為與功能,藉此將我們要測試的對象,與其相依的外部服務「隔離」。簡單來說,就是做一個外部服務的「仿冒品」。這裡的外部服務,可以是其他類別函數、外部API、檔案系統存取介面等等。
那為什麼需要 Mocking呢?大家可以試想幾種情境:
在以上的情況中,有一個很重要的共通點,那就是測試能否運行,都依賴於外部服務是否為我們所控制。為了破除這個依賴,Mocking 就由此而生了!
以下讓我們一個Mocking的例子:
app/Repositories/UserRepository.php
<?php
namespace App\Repositories;
use App\Models\User;
class UserRepository
{
protected $model;
public function __construct(User $model)
{
$this->model = $model;
}
public function getUserById($userId)
{
return $this->model::find($userId);
}
}
app/Repositories/PostRepository.php
<?php
namespace App\Repositories;
class PostRepository
{
protected $model;
}
app/Services/UserService.php
<?php
namespace App\Services;
use App\Repositories\PostRepository;
use App\Repositories\UserRepository;
class UserService
{
private $userRepository;
private $postRepository;
public function __construct(
PostRepository $postRepository,
UserRepository $userRepository
) {
$this->postRepository = $postRepository;
$this->userRepository = $userRepository;
}
public function getUserData(int $userId)
{
$user = $this->userRepository->getUserById($userId);
if (empty($user)) {
return [];
}
$user->posts = $this->postRepository->getPostsByUserId($userId);
return $user;
}
}
tests/Feature/UserServiceTest.php
<?php
namespace Tests\Feature;
use App\Models\User;
use App\Repositories\PostRepository;
use App\Repositories\UserRepository;
use App\Services\UserService;
use Tests\TestCase;
class UserServiceTest extends TestCase
{
public function testGetUserDataWhenUserNotFound()
{
$this->mock(UserRepository::class, function ($mock) {
$mock->shouldReceive('getUserById')
->with(1)
->once()
->andReturn(null);
});
$service = app(UserService::class);
$user = $service->getUserData(1);
$this->assertEmpty($user);
}
public function testGetUserData()
{
$user = User::factory()->make();
$this->mock(UserRepository::class, function ($mock) use ($user) {
$mock->shouldReceive('getUserById')
->with(1)
->once()
->andReturn($user);
});
$this->mock(PostRepository::class, function ($mock) {
$mock->shouldReceive('getPostsByUserId')
->with(1)
->once()
->andReturn([]);
});
$service = app(UserService::class);
$user = $service->getUserData(1);
$this->assertNotEmpty($user);
$this->assertNotNull($user->posts);
}
}
在以上測試程式碼中,我們撰寫了 2 個測試案例函數:
testGetUserDataWhenUserNotFound()
在這個函數中,我們 Mock 了 UserRepository→getUserById()
這個函數的行為,因此即便我們沒有準備測試資料,仍然可以測試「UserRepository→getUserById(1)
的回應為 null 」的這個情境下,UserService→getUserData(1)
是否可正常運作。
testGetUserData()
在這個函數中,這次我們 Mock 了 UserRepository→getUserById()
以 PostRepository→getPostsByUserId()
及這2個函數的行為,而這次我們甚至 Mock 了一個還沒實作的函數 PostRepository→getPostsByUserId()
!
以上便是今天的 Mocking 初體驗,是不是很有趣呢?
明天讓我們繼續探所更多關於 Mocking 的各種技巧吧!